JavaScript中的this

what’s this ?

最近稍稍有点空闲,复习一下基础知识。说起 JavaScript 这门语言让人头疼的地方, 就绕不开 this 关键字的指向问题。

经过孜孜不倦地翻阅各大 IT 牛人的博客和相关网站之后……
被虐的过程就不详细描述了!!

总之,学习完之后,我就 “人不住手” 地总结了一下学到的知识,分享给大家。


学习 this 的必要

众所周知,JavaScript 是一门基于原型的编程语言,我们可以通过使用关键词
newthis 来实现代码的复用。

通过学习 this 等相关知识,能帮助你更好了解这门语言的设计模式和特性,加深你对代码的理解与使用。

this 是什么?this 指向谁?

  1. this 是 JS 关键字,代表了一个空间地址。
  2. this 是一个指针,指向函数运行时所在的环境。

通俗讲,就是谁调用了函数,函数内的 this 就指向谁。因此,我们想知道 this 指向谁,就一定要清楚到底是谁调用了函数。(PS:哈哈哈,说了句废话。)

this 的四种调用

默认调用

独立函数调用时,this 指向 window 对象

1
2
3
4
5
6
7
8
9
// 注意:这里不能使用let
var a = 1 // 全局用let声明的变量不属于window

function fn(){
console.log(this.a)
}

// fn函数当前运行环境是 "window" 对象,所以this指向 全局window对象
fn() // 1

严格模式下,默认绑定的 this 指向 undefined 。

1
2
3
4
;(function() {
'use strict'
console.log(this) // undefined
})()

对象调用

如果函数执行时有上下文对象,会把函数里的 this 默认绑定到这个上下文对象上。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var a = 10

function fn(){
console.log(this.a);
}

let obj = {
a : 20,
fn : fn
}

// 对象obj调用了fn函数,fn的this指向当前的运行环境 "obj" 对象,输出20
obj.fn() // 20

// 因为 obj.fn 是引用类型,即 objFn === fn函数。
let objFn = obj.fn

// 独立调用函数 objFn,fn的this指向当前的运行环境 "window" 对象,输出10
objFn() // 10

多层调用时,默认获取最后一层调用的上下文对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var a = 10

function fn() {
console.log(this.a)
}

let obj = {
a: 20,
fn: fn
}

let obj2 = {
a: 30,
obj: obj
}

// 默认绑定最后调用的上下文,即this 指向 obj。
obj2.obj.fn() // 20

call、apply、bind 强制绑定

通过 call、apply、bind 改变函数的 this 指向。

绑定后无论怎么调用,也不会改变其 this 指向。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let obj = {
a: 10
}

let obj2 = {
a: 20,
fn() {
console.log(this.a)
}
}

function fn2() {
obj2.fn.call(obj) // 把obj2.fn的this指向对象obj
}
// 虽然执行环境是 "window",但是因为通过call强制绑定了this,所以输出10
fn2() // 10


new 绑定调用

老生常谈,先看看 JS 中 new 关键字做了什么:

  1. 在内存中创建一个新对象
  2. 将新对象的proto指向构造函数的原型 prototype 对象
  3. 将构造函数的作用域赋值给新对象,(this 指向新对象)
  4. 执行构造函数中的代码(给这个新对象加属性和方法)
  5. 返回新对象,如果这个函数没有返回其他对象。

得出结论:

使用 new 调用函数,返回的对象的 this 始终指向自身。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function fn() {
this.a = 10
console.log(this)
}
// obj 是从new出来的实例
var obj = new fn() // fn {a: 10}

// obj的this指向自身
console.log(obj.a) // 10

console.log(window.a) // undefined
// "window"环境下执行了fn函数,this指向fn

fn() // window对象

console.log(window.a) // 10


箭头函数的 this

箭头函数区别于以上介绍的运行规则,而是完全根据外部作用域来决定 this,但其父级是遵循 this 绑定规则的(即:如果箭头函数的父级的 this 指向 window,那么箭头函数的 this 也指向 window)

通过例子认识下:

1
2
3
4
5
6
7
let obj={
a:10,
fn:function(){
setTimeout(function(){console.log(this.a)})
}
};
obj.fn() // undefined

上述例子,可以发现在 setTimeout 中传入函数时,函数中的 this 会指向 window 对象,那么怎样让 this 指向 obj 对象?

修改下代码:

1
2
3
4
5
6
7
let obj={
a:10,
fn:function(){
setTimeout(()=>{console.log(this.a)});
}
};
obj.fn() // 10

发现在我们使用箭头函数之后,使其继承了父函数 obj.fn 的 this 指针,达到了如下相同效果:

1
2
3
4
5
6
7
let obj = {
a: 10,
fn: function() {
console.log(this.a)
}
}
obj.fn() // 10

看到这里,应该以及理解了箭头函数的 this 是继承来的意思。
让我们再通过几个例子方便去理解箭头函数中的 this。

例 1:

1
2
3
4
5
6
7
8
9
10
let obj = {
fn: () => {
console.log(this)
},
fn2() {
console.log(this)
}
}
obj.fn() // window对象
obj.fn2() // {fn: ƒ, fn2: ƒ}

相信你一定很疑惑,为什么上述箭头函数里输出的是 window 对象,那么再看一段代码就清楚了。

例 2:

1
2
3
4
let obj = {
that: this
}
console.log(obj.that) // window对象

我们发现,obj 里的 this 指向的是全局的 window 对象,那么就很好理解了,箭头函数继承了 obj 中的 this,也指向了全局 window 对象。

最后,我们可以通过箭头函数实现强制绑定 this 的效果。

没修改前的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var a = 10

let obj = {
a : 20,
fn() {
console.log(this.a);
}
}

obj.fn() // 20

let objFn = obj.fn

objFn() // 10

以上例子是我们讲解对象调用时的例子,我们不难发现,直接用 obj 对象去调用和独立调用时输出的结果不一致。如果你有仔细阅读文章的话,应该不难理解造成这种情形的原因。

接下来,我们通过箭头函数改造下代码,使我们复制的引用,独立调用时 this 也指向 obj。

修改后的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var a = 10

let obj = {
a: 20,
fn() {
return () => {
console.log(this.a)
}
}
}

obj.fn()() // 20

let objFn = obj.fn()

objFn() // 20

// 发现即使是用call强制绑定,也无法改变其this
objFn.call(window) // 20


END

无论怎样,都别丢了快乐和努力。

转载分享,请标明出处。



喜欢可以支持一下